package com.smartfast4j.backend.modules.sys.security.shiro;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.smartfast4j.backend.common.util.ShiroUtils;
import com.smartfast4j.backend.modules.sys.entity.SysUser;
import com.smartfast4j.backend.modules.sys.service.SysMenuService;
import com.smartfast4j.backend.modules.sys.service.SysUserService;
import com.smartfast4j.backend.modules.sys.vo.SysMenuVo;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

/**
 * 系统用户认证
 * Created by gacl on 2017/3/3.
 */
public class SysUserRealm extends AuthorizingRealm {

    private static final Logger logger = Logger.getLogger(SysUserRealm.class);

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private SysMenuService sysMenuService;

    /**
     * 授权(验证权限时调用)
     * SecurityUtils.getSubject().isPermitted（）这个方法时会调用doGetAuthorizationInfo（），
     * 一而我们的@RequiresPermissions这个注解其实就是在执行SecurityUtils.getSubject().isPermitted（）。
     * 我们在某个方法上加上@RequiresPermissions这个，那么我们访问这个方法的时候，就会自动调用SecurityUtils.getSubject().isPermitted（），
     * 从而去调用doGetAuthorizationInfo 匹配
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("调用doGetAuthorizationInfo验证用户权限");
        SysUser user = (SysUser) principalCollection.getPrimaryPrincipal();
        Long userId = user.getUserId();
        //用户权限列表
        List<String> permsList = null;
        //系统管理员，拥有最高权限
        if (userId == 1) {
            List<SysMenuVo> menuList = sysMenuService.queryAllMenuList();
            permsList = new ArrayList<>(menuList.size());
            for (SysMenuVo menu : menuList) {
                permsList.add(menu.getPerms());
            }
        } else {
            //根据用户ID查询用户权限
            permsList = sysUserService.queryAllPerms(userId);
        }
        //用户权限列表
        Set<String> permsSet = new HashSet<>();
        for (String perms : permsList) {
            if (StringUtils.isBlank(perms)) {
                continue;
            }
            permsSet.addAll(Arrays.asList(perms.trim().split(",")));
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    }

    /**
     * 认证(登录时调用)
     * 首先登录成功（此时会缓存相应的AuthenticationInfo），
     * 然后修改密码；此时密码就变了；接着需要调用Realm的clearCachedAuthenticationInfo方法清空之前缓存的AuthenticationInfo；
     * 否则下次登录时还会获取到修改密码之前的那个AuthenticationInfo；
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        logger.info("调用doGetAuthenticationInfo进行用户登录");
        /**
         * 获取用户输入的登录用户名
         */
        String userName = (String) authenticationToken.getPrincipal();
        /**
         * 获取用户输入的登录密码
         */
        //String password = new String((char[]) authenticationToken.getCredentials());

        //根据用户名查询用户信息
        SysUser searchCondition = new SysUser();
        searchCondition.setUsername(userName);
        EntityWrapper<SysUser> sysUserEntityWrapper = new EntityWrapper<>(searchCondition);
        SysUser sysUser = sysUserService.selectOne(sysUserEntityWrapper);
        //用户不存在
        if(sysUser == null){
            throw new UnknownAccountException("账号或密码不正确");
        }
        //password = ShiroUtils.sha256(password,sysUser.getSalt());
        //密码错误
       /* if(!password.equals(sysUser.getPassword())) {
            throw new IncorrectCredentialsException("账号或密码不正确");
        }*/
        //账号锁定（'状态  0：禁用   1：正常'）
        if(sysUser.getStatus() == 0){
            throw new LockedAccountException("账号已被锁定,请联系管理员");
        }
        //密码校验交由SimpleAuthenticationInfo内部去做，如果校验不通过就会抛出IncorrectCredentialsException异常
        /**
         * 使用Redis缓存时，ByteSource.Util.bytes(sysUser.getSalt())会导致出现：Caused by: java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource
         * 出现这种情况是因为：SimpleByteSource没有是实现Serializable接口
         * 解决办法：自定义一个类继承SimpleByteSource实现Serializable接口
         */
        return new SimpleAuthenticationInfo(sysUser, sysUser.getPassword(), new MySimpleByteSource(sysUser.getSalt()),getName());
    }

    /**
     * 注入加密算法匹配密码时使用
     * @param credentialsMatcher
     */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
        shaCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);
        shaCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);
        super.setCredentialsMatcher(shaCredentialsMatcher);
    }

    /**
     * 清除缓存
     */
    public void clearCached() {
        //清除登录认证缓存信息
       clearCachedAuthenticationInfo();
        //清除身份验证缓存信息
        clearCachedAuthorizationInfo();
    }

    /**
     * 清除登录认证缓存信息
     */
    public void clearCachedAuthenticationInfo(){
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        Cache c = getAuthenticationCache();
        logger.info("清除【登录认证】缓存之前");
        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }
        super.clearCachedAuthenticationInfo(principals);
        logger.info("调用父类清除【登录认证】缓存之后");
        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }

        // 添加下面的代码清空【登录认证】的缓存
        SysUser user = (SysUser)principals.getPrimaryPrincipal();
        SimplePrincipalCollection spc = new SimplePrincipalCollection(user.getUsername(),getName());
        super.clearCachedAuthenticationInfo(spc);
        logger.debug("调用clearCachedAuthenticationInfo清除登录用户"+user.getUsername()+"的【登录认证】缓存信息");
        logger.info("添加了代码清除【登录认证】缓存之后");
        int cacheSize = c.keys().size();
        logger.info("【登录认证】缓存的大小:" + c.keys().size());
        if (cacheSize == 0){
            logger.info("说明【登录认证】缓存被清空了。");
        }
    }

    /**
     * 清除授权缓存信息
     */
    public void clearCachedAuthorizationInfo(){
        logger.info("清除【授权缓存】之前");
        Cache c = getAuthorizationCache();
        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        SysUser user = (SysUser)principals.getPrimaryPrincipal();
        super.clearCachedAuthorizationInfo(principals);
        logger.debug("调用clearCachedAuthorizationInfo清除登录用户"+user.getUsername()+"的【授权验证】缓存信息");
        logger.info("清除【授权缓存】之后");
        int cacheSize = c.keys().size();
        logger.info("【授权缓存】的大小:" + cacheSize);

        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }
        if(cacheSize == 0){
            logger.info("说明【授权缓存】被清空了。");
        }
    }

    /**
     * 重写doClearCache方法的实现
     * 使用SecurityUtils.getSubject().logout()注销登录用户时会回调这个方法;
     * @param principals
     */
   /* @Override
    protected void doClearCache(PrincipalCollection principals) {
        SysUser user = (SysUser)principals.getPrimaryPrincipal();
        logger.debug("调用doClearCache清除登录用户"+user.getUsername()+"的缓存信息");
        clearCached();
    }*/

    /**
     * 重写clearCachedAuthenticationInfo方法，清除登录认证缓存
     * 使用SecurityUtils.getSubject().logout()注销登录用户时会回调这个方法;
     * @param principals
     */
    /*@Override
    protected void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        Cache c = getAuthenticationCache();
        logger.info("清除【认证】缓存之前");
        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }
        super.clearCachedAuthenticationInfo(principals);
        logger.info("调用父类清除【认证】缓存之后");
        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }

        // 添加下面的代码清空【认证】的缓存
        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        SimplePrincipalCollection spc = new SimplePrincipalCollection(user.getUsername(),getName());
        super.clearCachedAuthenticationInfo(spc);
        logger.info("添加了代码清除【认证】缓存之后");
        int cacheSize = c.keys().size();
        logger.info("【认证】缓存的大小:" + c.keys().size());
        if (cacheSize == 0){
            logger.info("说明【认证】缓存被清空了。");
        }
    }*/

    /**
     * 重写clearCachedAuthorizationInfo方法，清除授权缓存
     * 使用SecurityUtils.getSubject().logout()注销登录用户时会回调这个方法;
     * @param principals
     */
    /*@Override
    protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        logger.info("清除【授权】缓存之前");
        Cache c = getAuthorizationCache();
        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }
        super.clearCachedAuthorizationInfo(principals);
        logger.info("清除【授权】缓存之后");
        int cacheSize = c.keys().size();
        logger.info("【授权】缓存的大小:" + cacheSize);

        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }
        if(cacheSize == 0){
            logger.info("说明【授权】缓存被清空了。");
        }
    }*/
}
